using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Resources;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using Fuzzware.Common;
using Fuzzware.Common.XML;
using Fuzzware.Common.Encoding;
using Fuzzware.Common.DataSchema;

namespace Fuzzware.ConvertFromXML.Processors
{
    public class SchemaAttributeCommands
    {
        public const String treatAsEncoded = "treatAsEncoded";
        public const String escapeString = "EscapeStr";
        public const String dontOutput = "DontOutput";
        public enum treatAsEncodedType
        {
            None,
            Binary,
            String
        }

        static String FuzzNS;
        static XmlSchemaSet FuzzSchemaSet;
        // If at some future date lots more commands are added, this should be changed to a SortedDictionary.
        static SortedDictionary<XMLObjectIdentifier, Dictionary<String, String>> TypeCommands;

        private static void LoadAndCompileSchema()
        {
            // Compile the schema, we want any error thrown here to be propogated up
            FuzzSchemaSet = XMLHelper.LoadAndCompileSchema("SchemaAttributeCommands.xsd", Assembly.GetExecutingAssembly(), null, out FuzzNS);
        }

        /// <summary>
        /// Attribute commands can exists on Element (and references to Elements) and Type nodes in a schema, so the commands from both must 
        /// be combined.  Element commands take precedence over Type commands.
        /// </summary>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        //private static XmlAttribute[] GetAllAttributeCommands(XmlQualifiedName TypeName, XmlSchemaSet oSchemaSet)
        //{
        //    return SchemerGlobalInfo.NameParticleDictionary[TypeName].UnhandledAttributes;

            //XmlSchemaElement Element = XMLHelper.GetSchemaElement(TypeName);
            //if (null == Element)
            //{
            //    Log.Write(MethodBase.GetCurrentMethod(), "Could not find a schema element of type '" + TypeName.ToString() + "'", Log.LogType.Warning);
            //    return new XmlAttribute[0];
            //}
            
            //List<XmlAttribute> Attributes = new List<XmlAttribute>();

            //// Add all the element commands
            //if(null != Element.UnhandledAttributes)
            //    for (int i = 0; i < Element.UnhandledAttributes.Length; i++)
            //    {
            //        Attributes.Add(Element.UnhandledAttributes[i]);
            //    }

            //// If this element references another element, add the actual element commands
            //if ((null != Element.RefName) && !Element.RefName.IsEmpty)
            //{
            //    XmlSchemaElement RefElement = oSchemaSet.GlobalElements[Element.RefName] as XmlSchemaElement;
            //    if(null == RefElement)
            //        Log.Write(MethodBase.GetCurrentMethod(), "Could not find the reference schema element of type '" + Element.RefName.ToString() + "'", Log.LogType.Warning);
            //    if(null != RefElement.UnhandledAttributes)
            //        for (int i = 0; i < RefElement.UnhandledAttributes.Length; i++)
            //        {
            //            if(!Attributes.Contains(RefElement.UnhandledAttributes[i]))
            //                Attributes.Add(RefElement.UnhandledAttributes[i]);
            //        }
            //}

            //// Add all commands on the type
            //XmlSchemaType Type = XMLHelper.GetTypeFromSchema(oSchemaSet, TypeName);
            //if (null != Type.UnhandledAttributes)
            //    for (int i = 0; i < Type.UnhandledAttributes.Length; i++)
            //    {
            //        if(!Attributes.Contains(Type.UnhandledAttributes[i]))
            //            Attributes.Add(Type.UnhandledAttributes[i]);
            //    }
    
            // If no Element commands, return the Type commands
            //if (null == Attributes)
            //{
            //    Attributes = Type.UnhandledAttributes;
            //}
            //else
            //{
            //    // Need to combine the element and type commands.  For each Type command, see if it is overridden by an Element command,
            //    // otherwise add it the array.
            //    if (null != Type.UnhandledAttributes)
            //    {
            //        for (int i = 0; i < Type.UnhandledAttributes.Length; i++)
            //        {
            //            bool found = false;
            //            for (int j = 0; j < Element.UnhandledAttributes.Length; j++)
            //            {
            //                if (Type.UnhandledAttributes[i].LocalName == Attributes[j].LocalName)
            //                {
            //                    found = true;
            //                    break;
            //                }
            //            }
            //            if (!found)
            //            {
            //                // Add new command
            //                XmlAttribute[] temp = new XmlAttribute[Attributes.Length + 1];
            //                Array.Copy(Attributes, temp, Attributes.Length);
            //                temp[Attributes.Length] = Type.UnhandledAttributes[i];
            //                Attributes = temp;
            //            }
            //        }
            //    }
            //}

            //return Attributes.ToArray();
        //}

        /// <summary>
        /// Returns the value of a command (attribute from the FuzzerTypeCommands namespace) for the specified schema set.
        /// </summary>
        /// <param name="oSchemaSet">The schema containing the fuzz commands as attributes of types</param>
        /// <param name="TypeName">The type containing the fuzz commands</param>
        /// <param name="CommandName">The commmand being looked for</param>
        /// <param name="CommandValue">The value of the command.</param>
        /// <returns>If true, the command was found, otherwise false, the command was not found.</returns>
        private static bool GetCommandValueForType(OutputSettings Settings, XMLObjectIdentifier TypeName, String CommandName, ref String CommandValue)
        {
            if ((null == TypeName) || !(TypeName is XMLElementIdentifier))
                return false;

            if (null == FuzzSchemaSet)
                SchemaAttributeCommands.LoadAndCompileSchema();

            if(null == TypeCommands)
                TypeCommands = new SortedDictionary<XMLObjectIdentifier, Dictionary<string, string>>();

            if(TypeCommands.ContainsKey(TypeName))
            {
                Dictionary<String, String> CommandVals = TypeCommands[TypeName];
                if (CommandVals.ContainsKey(CommandName))
                {
                    CommandValue = CommandVals[CommandName];
                    return true;
                }
                // Since when we add one command, we add all commands, if the command is not found but the type exists in the dictionary,
                // then the command does not exist.
                return false;
            }

            // If we get to here then the TypeName does not exist in the cache
            
            // Get all commands for this type, for efficiency's sake.
            XmlAttribute[] Attributes = null;
            try
            {
                Attributes = (Settings.ObjectDB[TypeName] as ElementDBEntry).UnhandledAttributes;
            }
            catch (Exception e)
            {
                if ((e is KeyNotFoundException) && String.IsNullOrEmpty(TypeName.QualifiedName.Namespace))
                    Log.Write(MethodBase.GetCurrentMethod(), "The type '" + TypeName.QualifiedName.ToString() + "' has no namespace, this is " +
                        "usually due to the XML file not having a namespace.  The XML file should always have a namespace, even if it requires a token " +
                        "root node with one declared that will not appear in the output.", Log.LogType.Error);
                Log.Write(e);
            }
            
            Dictionary<String, String> CommandValues = new Dictionary<string,string>();
            
            // Add all commands, and take note if we find the command we are interested in
            bool CommandFound = false;
            if (null != Attributes)
            {
                for (int i = 0; i < Attributes.Length; i++)
                {
                    // Add all the command values for this type.  Only add those of the right namespace.
                    if (Attributes[i].NamespaceURI == FuzzNS)
                    {
                        CommandValues.Add(Attributes[i].LocalName, Attributes[i].Value);
                        if (Attributes[i].LocalName == CommandName)
                        {
                            // We found the command
                            CommandValue = Attributes[i].Value;
                            CommandFound = true;
                        }
                    }
                }
            }

            // Add to dictionary
            TypeCommands.Add(TypeName, CommandValues);

            if(CommandFound)
                return true;

            // We didn't find the command.  The command must be valid as otherwise an error would have occurred when the fuzz
            // schema was being compiled, so their is no error/exception if we don't find it.
            return false;
        }

        private static bool SetCommandValueForType(OutputSettings Settings, XMLObjectIdentifier TypeName, String CommandName, String CommandValue)
        {
            String CurrentCommandValue = "";
            // Get the current value, this will ensure the value is cached (if it exists) so we only need ever change the cache
            if (GetCommandValueForType(Settings, TypeName, CommandName, ref CurrentCommandValue))
            {
                // Check to see if the value is already the desired value
                if (!CurrentCommandValue.Equals(CommandValue, StringComparison.CurrentCultureIgnoreCase))
                {
                    // Change the value in the cache
                    TypeCommands[TypeName][CommandName] = CommandValue;
                }
            }
            else
            {
                // Add the type name to the cache is necessary
                Dictionary<String, String> CommandVals = null;
                if (!TypeCommands.ContainsKey(TypeName))
                {
                    CommandVals = new Dictionary<string,string>();
                    TypeCommands.Add(TypeName, CommandVals);
                }
                CommandVals = TypeCommands[TypeName];
                // Set the value of the command
                CommandVals.Add(CommandName, CommandValue);
            }
            return true;
        }

        /// <summary>
        /// The function returns false if the 'markup' command was not found, however a default value is passed back.
        /// </summary>
        /// <param name="oSchemaSet"></param>
        /// <param name="TypeName"></param>
        /// <param name="RemoveMarkup"></param>
        /// <returns></returns>
        public static bool GetRemoveMarkupOnOutput(OutputSettings Settings, XMLObjectIdentifier TypeName, out NodeProcessor.MarkupType Markup)
        {
            String CommandValue = null;
            bool UsingDefault = false;
            // Although we assign a default value, we will always assign a value or error.
            Markup = NodeProcessor.MarkupType.dontRemove;

            if (!SchemaAttributeCommands.GetCommandValueForType(Settings, TypeName, NodeProcessor.Markup, ref CommandValue))
            {
                // Get the default value;
                XmlSchemaAttribute Att = FuzzSchemaSet.GlobalAttributes[new XmlQualifiedName(NodeProcessor.Markup, FuzzNS)] as XmlSchemaAttribute;
                CommandValue = Att.DefaultValue;
                UsingDefault = true;
            }

            try
            {
                Markup = (NodeProcessor.MarkupType)Enum.Parse(typeof(NodeProcessor.MarkupType), CommandValue);
                return !UsingDefault;
            }
            catch(Exception)
            {
                // The value was not valid
                Log.Write(MethodBase.GetCurrentMethod(), "Invalid Value : '" + CommandValue + "'", Log.LogType.Error);
            }

            return false;
        }

        /// <summary>
        /// The function returns false if the 'Encoding' command was not found, and Encoding = None is passed back.
        /// </summary>
        /// <param name="oSchemaSet"></param>
        /// <param name="TypeName"></param>
        /// <param name="Encoding"></param>
        /// <returns></returns>
        public static bool GetEncoding(OutputSettings Settings, XMLObjectIdentifier TypeName, out Coder.EncodingType Encoding)
        {
            String CommandValue = null;
            Encoding = Coder.EncodingType.None;

            if (!SchemaAttributeCommands.GetCommandValueForType(Settings, TypeName, Coder.Encoding, ref CommandValue))
            {
                // No default value;
                return false;
            }

            try
            {
                Encoding = (Coder.EncodingType)Enum.Parse(typeof(Coder.EncodingType), CommandValue);
                return true;
            }
            catch (Exception)
            {
                // The value was not valid
                Log.Write(MethodBase.GetCurrentMethod(), "Invalid Value : '" + CommandValue + "'", Log.LogType.Error);
            }

            return false;
        }

        public static bool GetTreatAsEncoded(OutputSettings Settings, XMLObjectIdentifier TypeName, out DataSchemaTypeCode treatAsEncoded)
        {
            String CommandValue = null;
            bool UsingDefault = false;
            // Although we assign a default value, we will always assign a value or error.
            treatAsEncoded = DataSchemaTypeCode.None;

            if (!SchemaAttributeCommands.GetCommandValueForType(Settings, TypeName, SchemaAttributeCommands.treatAsEncoded, ref CommandValue))
            {
                // Get the default value;
                XmlSchemaAttribute Att = FuzzSchemaSet.GlobalAttributes[new XmlQualifiedName(SchemaAttributeCommands.treatAsEncoded, FuzzNS)] as XmlSchemaAttribute;
                CommandValue = Att.DefaultValue;
                UsingDefault = true;
            }

            try
            {
                treatAsEncoded = (DataSchemaTypeCode)Enum.Parse(typeof(DataSchemaTypeCode), CommandValue);
                return !UsingDefault;
            }
            catch (Exception)
            {
                // The value was not valid
                Log.Write(MethodBase.GetCurrentMethod(), "Invalid Value : '" + CommandValue + "'", Log.LogType.Error);
            }

            return false;
        }

        public static bool GetOutputAs(OutputSettings Settings, XMLObjectIdentifier TypeName, out Coder.OutputAsType outputAs)
        {
            String CommandValue = null;
            bool UsingDefault = false;
            // Although we assign a default value, we will always assign a value or error.
            outputAs = Coder.OutputAsType.Unchanged;

            if (!SchemaAttributeCommands.GetCommandValueForType(Settings, TypeName, Coder.OutputAs, ref CommandValue))
            {
                // Get the default value;
                XmlSchemaAttribute Att = FuzzSchemaSet.GlobalAttributes[new XmlQualifiedName(Coder.OutputAs, FuzzNS)] as XmlSchemaAttribute;
                CommandValue = Att.DefaultValue;
                UsingDefault = true;
            }

            try
            {
                outputAs = (Coder.OutputAsType)Enum.Parse(typeof(Coder.OutputAsType), CommandValue);
                return !UsingDefault;
            }
            catch (Exception)
            {
                // The value was not valid
                Log.Write(MethodBase.GetCurrentMethod(), "Invalid Value : '" + CommandValue + "'", Log.LogType.Error);
            }

            return false;
        }

        public static bool SetOutputAs(OutputSettings Settings, XMLObjectIdentifier TypeName, String CommandValue)
        {
            Coder.OutputAsType outputAs = Coder.OutputAsType.Unchanged;

            // See if the outputAs value is a legitmate value
            try
            {
                outputAs = (Coder.OutputAsType)Enum.Parse(typeof(Coder.OutputAsType), CommandValue);
            }
            catch (Exception)
            {
                // The value was not valid
                Log.Write(MethodBase.GetCurrentMethod(), "Cannot set outputAs for '" + TypeName.QualifiedName.ToString() + "' to '" + CommandValue + "'", Log.LogType.Warning);
                return false;
            }

            if (SchemaAttributeCommands.SetCommandValueForType(Settings, TypeName, Coder.OutputAs, CommandValue))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Returns the EscapeStr's that act as exceptions for terminating strings e.g. terminating string is ')', but not '\)', the
        /// EscapeStr = "\" will mean Convert2XML will not match "\)".
        /// </summary>
        /// <returns></returns>
        public static bool GetEscapeStr(OutputSettings Settings, XMLObjectIdentifier TypeName, out String escapeStr)
        {
            String CommandValue = null;
            escapeStr = "";
            // Although we assign a default value, we will always assign a value or error.
            if (SchemaAttributeCommands.GetCommandValueForType(Settings, TypeName, SchemaAttributeCommands.escapeString, ref CommandValue))
            {
                escapeStr = CommandValue;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Gets whether the node should be serialised for output.
        /// </summary>
        /// <returns></returns>
        public static bool GetDontOutput(OutputSettings Settings, XMLObjectIdentifier TypeName, out bool bDontOutput)
        {
            String CommandValue = null;
            bDontOutput = false;
            // Although we assign a default value, we will always assign a value or error.
            if (SchemaAttributeCommands.GetCommandValueForType(Settings, TypeName, SchemaAttributeCommands.dontOutput, ref CommandValue))
            {
                if (Boolean.TrueString.Equals(CommandValue, StringComparison.InvariantCultureIgnoreCase))
                    bDontOutput = true;
                return true;
            }
            return false;
        }
    }
}
